#include "glare.d3d11.DrawContext.h"
#include "glare.d3d11.GpuBuffer.h"
#include "glare.d3d11.Driver.h"
#include "glare.d3d11.RenderTarget.Interface.h"
#include "glare.core.RenderTarget.h"
#include "glare.d3d11.VertexShader.h"
#include "glare.d3d11.PixelShader.h"
#include "glare.core.GpuBuffer.h"
#include "glare.d3d11.ShaderInputDeclaration.h"
#include "glare.d3d11.Texture.h"
#include "glare.d3d11.SamplerState.h"
#include "glare.d3d11.GpuResourceFactory.h"
#include "glare.d3d11.ShaderResourceView.h"

namespace Glare {

	D3D11DrawContext::D3D11DrawContext(D3D11Driver* driver, ID3D11DeviceContext* d3d_device_context) 
		: _driver(driver)
	{
		_d3d_device_context = d3d_device_context;
	}

	D3D11DrawContext::~D3D11DrawContext()
	{
		if (_d3d_device_context)
			_d3d_device_context->ClearState();

		GLARE_SAFE_RELEASE(_d3d_device_context, "Error while releasing D3D device context");
	}

	void D3D11DrawContext::ClearRenderTarget(RenderTarget* render_target, const ColourValue& clear_color)
	{
		D3D11RenderTargetInterface* d3d11_interface = static_cast<D3D11RenderTargetInterface*>(render_target->_GetInterface());

		if (!d3d11_interface)
		{
			_driver->WriteLog("D3D11DrawContext::ClearRenderTarget: internal error, d3d11_interface was NULL", LMT_Error);
			return;
		}

		float clear_rgba[4] = { clear_color.r, clear_color.g, clear_color.b, clear_color.a };
		_d3d_device_context->ClearRenderTargetView(d3d11_interface->GetD3DRenderTargetView(), clear_rgba);
	}

	void D3D11DrawContext::Draw(const uint& vertex_count, const uint& start_vertex)
	{
		_d3d_device_context->Draw(vertex_count, start_vertex);
	}

	void D3D11DrawContext::DrawIndexed(const uint& index_count, const uint& start_index, const uint& vertex_offset)
	{
		_d3d_device_context->DrawIndexed(index_count, start_index, vertex_offset);
	}

	void D3D11DrawContext::UpdateGpuResource(GpuResource* destination_resource, const uint& dest_sub_resource, Box* dest_box, const void* src_data, const uint& src_row_pitch, const uint& src_depth_pitch)
	{
		D3D11GpuResourceInterface* d3d11_interface = static_cast<D3D11GpuResourceInterface*>(destination_resource->_GetInterface());
		_d3d_device_context->UpdateSubresource(d3d11_interface->GetD3DResource(), dest_sub_resource, (D3D11_BOX*)dest_box, src_data, src_row_pitch, src_depth_pitch);
	}

	void D3D11DrawContext::IA_SetIndexBuffer(GpuBuffer* buffer, const bool& format_32bit, const uint& offset)
	{
		auto desc = buffer->GetDescription();

		if ((desc.BindFlags & GpuResourceBind::IndexBuffer) == 0)
			throw Exception("BindFlag for IndexBuffer not set");

		D3D11GpuBuffer* d3d_buffer = static_cast<D3D11GpuBuffer*>(buffer);

		_d3d_device_context->IASetIndexBuffer(
			d3d_buffer->GetD3DBuffer(), 
			format_32bit ? DXGI_FORMAT_R32_UINT : DXGI_FORMAT_R16_UINT, 
			offset);
	}

	void D3D11DrawContext::IA_SetVertexBuffer(const GpuVertexBufferBinding& vertex_buffer_binding, const uint& start_slot)
	{
		D3D11GpuBuffer* glare_d3d11_buffer = static_cast<D3D11GpuBuffer*>(vertex_buffer_binding.Buffer);
		ID3D11Buffer* d3d_buffer = nullptr;

		if (glare_d3d11_buffer)
			d3d_buffer= glare_d3d11_buffer->GetD3DBuffer();

		_d3d_device_context->IASetVertexBuffers(start_slot, 1, &d3d_buffer, &vertex_buffer_binding.Stride, &vertex_buffer_binding.Offset);
	}

	void D3D11DrawContext::IA_SetVertexBuffers(const GpuVertexBufferBinding bindings[], const uint& num_bindings, const uint& start_slot)
	{
		if (num_bindings < 1)
			return;

		for (uint i = 0; i < num_bindings; ++i)
		{
			const GpuVertexBufferBinding& binding = bindings[i];

			_d3d_vertex_buffer_strides[i] = binding.Stride;
			_d3d_vertex_buffer_offsets[i] = binding.Offset;

			// pass on the NULL pointer to D3D
			if (!binding.Buffer)
			{
				_d3d_vertex_buffer_array[i] = NULL;
				continue;
			}

			ID3D11Buffer* d3d11_buffer = static_cast<D3D11GpuBuffer*>(binding.Buffer)->GetD3DBuffer();

			// the GpuVertexBufferBinding returned a NULL pointer for the D3D11 buffer
			if (!d3d11_buffer)
			{
				_driver->WriteLog("D3D11DrawContext::IA_SetVertexBuffers: internal error, d3d11_buffer was NULL", LMT_Warning);
				_d3d_vertex_buffer_array[i] = NULL;
				continue;
			}

			_d3d_vertex_buffer_array[i] = d3d11_buffer;
		}
	}

	void D3D11DrawContext::IA_SetPrimitiveType(const GpuPrimitive::Type& primitive_type)
	{
		_d3d_device_context->IASetPrimitiveTopology((D3D11_PRIMITIVE_TOPOLOGY)primitive_type);
	}

	void D3D11DrawContext::IA_SetInputDeclaration(ShaderInputDeclaration* declaration)
	{
		if (!declaration)
		{
			_d3d_device_context->IASetInputLayout(NULL);
			return;
		}

		D3D11ShaderInputDeclaration* d3d11_declaration = static_cast<D3D11ShaderInputDeclaration*>(declaration);

		if (!d3d11_declaration)
			return;

		_d3d_device_context->IASetInputLayout(d3d11_declaration->GetD3DInputLayout());
	}

	void D3D11DrawContext::VT_SetWorldMatrix(const Matrix4 &m)
	{
	}

	void D3D11DrawContext::VT_SetViewMatrix(const Matrix4 &m)
	{
	}

	void D3D11DrawContext::VT_SetProjectionMatrix(const Matrix4 &m)
	{
	}

	void D3D11DrawContext::VS_SetVertexShader(VertexShader* shader)
	{
		D3D11VertexShader* vertex_shader = static_cast<D3D11VertexShader*>(shader);
		_d3d_device_context->VSSetShader(vertex_shader->GetD3DVertexShader(), NULL, 0);
	}

	void D3D11DrawContext::VS_SetConstantBuffer(const uint& slot, GpuBuffer* constant_buffer)
	{
		if (!constant_buffer)
		{
			// TODO: this is possible/safe without this variable ? e.g. &((ID3D11Buffer*)NULL)
			ID3D11Buffer* reset = NULL;
			_d3d_device_context->VSSetConstantBuffers(0, 1, &reset);
			return;
		}

		D3D11GpuBuffer* glare_buffer = static_cast<D3D11GpuBuffer*>(constant_buffer);
		ID3D11Buffer* d3d11_buffer = glare_buffer->GetD3DBuffer();

		_d3d_device_context->VSSetConstantBuffers(slot, 1, &d3d11_buffer);
	}

	void D3D11DrawContext::RS_SetViewport(const Viewport& viewport)
	{
		_d3d_device_context->RSSetViewports(1, (const D3D11_VIEWPORT*)&viewport);
	}

	void D3D11DrawContext::RS_SetViewports(uint num_viewports, const Viewport* viewport_array)
	{
		_d3d_device_context->RSSetViewports(1, (const D3D11_VIEWPORT*)viewport_array);
	}

	void D3D11DrawContext::PS_SetPixelShader(PixelShader* shader)
	{
		D3D11PixelShader* pixel_shader = static_cast<D3D11PixelShader*>(shader);
		_d3d_device_context->PSSetShader(pixel_shader->GetD3DPixelShader(), NULL, 0);
	}

	void D3D11DrawContext::PS_SetSamplerState(const uint& slot, SamplerState* sampler_state)
	{
		ID3D11SamplerState* d3d11_sampler_state = static_cast<D3D11SamplerState*>(sampler_state)->GetD3DSamplerState();
		_d3d_device_context->PSSetSamplers(slot, 1, &d3d11_sampler_state);
	}

	void D3D11DrawContext::PS_SetShaderResource(const uint& slot, ShaderResourceView* resource_view)
	{
		ID3D11ShaderResourceView* d3d11_srv = static_cast<D3D11ShaderResourceView*>(resource_view)->GetD3DShaderResourceView();
		_d3d_device_context->PSSetShaderResources(slot, 1, &d3d11_srv);
	}

	void D3D11DrawContext::OM_SetRenderTarget(RenderTarget* render_target, DepthBuffer* depth_buffer)
	{
		ID3D11RenderTargetView* rt_view = NULL;

		if (render_target)
		{
			D3D11RenderTargetInterface* d3d11_interface = static_cast<D3D11RenderTargetInterface*>(render_target->_GetInterface());

			if (!d3d11_interface)
			{
				_driver->WriteLog("D3D11DrawContext::IA_SetVertexBuffers: internal error, d3d11_buffer was NULL", LMT_Error);
				return;
			}

			rt_view = d3d11_interface->GetD3DRenderTargetView();
		}

		_d3d_device_context->OMSetRenderTargets(1, &rt_view, NULL);
	}

	void D3D11DrawContext::OM_SetMultiRenderTargets(RenderTarget** render_targets, int num_targets, DepthBuffer* depth_buffer)
	{
		if (num_targets < 1)
			return;

		for (int i = 0; i < num_targets; ++i)
		{
			// pass on the NULL pointer to D3D
			if (!render_targets[i])
			{
				_d3d_rt_view_array[i] = NULL;
				continue;
			}

			D3D11RenderTargetInterface* d3d11_interface = static_cast<D3D11RenderTargetInterface*>(render_targets[i]->_GetInterface());

			// the RenderTarget returned a NULL pointer for the D3D11 interface
			if (!d3d11_interface)
			{
				_driver->WriteLog("D3D11DrawContext::OM_SetMultiRenderTargets: internal error, d3d11_interface was NULL", LMT_Warning);
				_d3d_rt_view_array[i] = NULL;
				continue;
			}

			_d3d_rt_view_array[i] = d3d11_interface->GetD3DRenderTargetView();
		}

		// TODO: get depth stencil view from depth buffer
		_d3d_device_context->OMSetRenderTargets(num_targets, _d3d_rt_view_array, NULL);
	}

	Driver* D3D11DrawContext::GetDriver() const
	{
		return _driver;
	}

	ID3D11DeviceContext* D3D11DrawContext::GetD3DDeviceContext() const
	{
		return _d3d_device_context;
	}

}